﻿using CrashMeManaged;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace DumpMe
{


    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class DumpMeWindow : Window
    {
        public DumpMeWindow()
        {
            InitializeComponent();

            DataContext = ((App)Application.Current).ViewModel;

            // first chance
            AppDomain.CurrentDomain.FirstChanceException += new EventHandler<FirstChanceExceptionEventArgs>(AppDomain_CurrentDomain_FirstChanceException);

            // Main Dispatcher (UI thread) event associate to the application of the current AppDomain
            Application.Current.Dispatcher.UnhandledExceptionFilter += new DispatcherUnhandledExceptionFilterEventHandler(Application_Current_Dispatcher_UnhandledExceptionFilter);

            Application.Current.Dispatcher.UnhandledException += new DispatcherUnhandledExceptionEventHandler(Application_Current_Dispatcher_UnhandledException);

            // http://msdn.microsoft.com/query/dev11.query?appId=Dev11IDEF1&l=EN-US&k=k(System.Windows.Application.DispatcherUnhandledException);k(DispatcherUnhandledException);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5);k(DevLang-csharp)&rd=true
            // DispatcherUnhandledException is raised : by an Application for each exception that is unhandled by code running on the main UI thread.
            // DispatcherUnhandledException is NOT raised : Background user interface (UI) thread (a thread with its own Dispatcher) or a background worker thread (a thread without a Dispatcher) exption are not forwarded to the Main Ui thread
            Application.Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(Application_Current_DispatcherUnhandledException); ;

            // catch them all
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomain_CurrentDomain_UnhandledException);
        }

        #region Exception handlers

        /// <summary>
        /// custom exception handler for the application dispatcher
        /// </summary>        
        private static void AppDomain_CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
        {
            Application.Current.Dispatcher.InvokeAsync(new Action(() =>
            {
                var vm = ((App)Application.Current).ViewModel;
                int pos = vm.Add("AppDomain_CurrentDomain_FirstChanceException");
                MiniDumpWriter.Write(pos, "AppDomain_CurrentDomain_FirstChanceException");
            }));

        }

        /// <summary>
        /// custom exception handler for the application dispatcher
        /// </summary>        
        private static void Application_Current_Dispatcher_UnhandledExceptionFilter(object sender, DispatcherUnhandledExceptionFilterEventArgs e)
        {
            Application.Current.Dispatcher.InvokeAsync(new Action(() => {
                var vm = ((App)Application.Current).ViewModel;
                int pos = vm.Add("Application_Current_Dispatcher_UnhandledExceptionFilter");
                MiniDumpWriter.Write(pos, "Application_Current_Dispatcher_UnhandledExceptionFilter");
            }));

            if (MessageBox.Show("Exception : " + e.Exception.GetType().ToString() + "\nCatch this exception ?", "ApplicationDispatcher_UnhandledExceptionFilter", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                e.RequestCatch = false;
            }
        }

        /// <summary>
        /// custom exception handler for the application dispatcher
        /// </summary>        
        private static void Application_Current_Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            Application.Current.Dispatcher.InvokeAsync(new Action(() =>
            {
                var vm = ((App)Application.Current).ViewModel;
                int pos = vm.Add("Application_Current_Dispatcher_UnhandledException");
                MiniDumpWriter.Write(pos, "Application_Current_Dispatcher_UnhandledException");
            }));

        }

        /// <summary>
        /// custom exception handler for the application dispatcher
        /// </summary>        
        private void Application_Current_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            Application.Current.Dispatcher.InvokeAsync(new Action(() =>
            {
                var vm = ((App)Application.Current).ViewModel;
                int pos = vm.Add("Application_Current_DispatcherUnhandledException");
                MiniDumpWriter.Write(pos, "Application_Current_DispatcherUnhandledException");
            }));
        }

        /// <summary>
        /// custom exception handler for an application domain
        /// </summary>
        private void AppDomain_CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Application.Current.Dispatcher.InvokeAsync(new Action(() =>
            {
                var vm = ((App)Application.Current).ViewModel;
                int pos = vm.Add("AppDomain_CurrentDomain_UnhandledException");
                MiniDumpWriter.Write(pos, "AppDomain_CurrentDomain_UnhandledException");
            }));

            if (e.IsTerminating == true)
            {
                MessageBox.Show("Is Terminating", "AppDomain_CurrentDomain_UnhandledException");
            }
        }

        #endregion

        #region Exception thrower

        public static void ThrowException(Exception e)
        {
            throw e;
        }

        public static void ThrowExceptionInTry(Exception e)
        {
            try
            {
                throw e;
            }
            finally
            {
                Application.Current.Dispatcher.InvokeAsync(new Action(() => {
                    var vm = ((App)Application.Current).ViewModel;
                    int pos = vm.Add("finally");
                    MiniDumpWriter.Write(pos, "finally");
                }));
            }
        }

        private static Exception GetException()
        {
            CrashMeWindow w = new CrashMeWindow();

            var r = w.ShowDialog();

            return w.ExceptionToTrow;
        }

        /// <summary>
        /// Do 
        /// </summary>
        private void ThrowException_MainThread()
        {
            Exception e = GetException();

            ThrowExceptionInTry(e);
        }

        /// <summary>
        /// Do in a thread
        /// </summary>
        private void ThrowException_Thread()
        {
            Exception e = GetException();

            ThreadStart threadStart = delegate() { ThrowExceptionInTry(e); };
            Thread thread = new Thread(threadStart);

            thread.Start();
        }

        /// <summary>
        /// Do in a thread
        /// </summary>
        private void ThrowException_BackgroundUiThread()
        {
            ThreadStart threadStart = delegate() 
            {
                CrashMeWindow w = new CrashMeWindow(new Action<Exception>((e) => ThrowExceptionInTry(e)));
                w.Closed += (sender1, e1) => w.Dispatcher.InvokeShutdown();
                w.ShowDialog();
                System.Windows.Threading.Dispatcher.Run();
            };

            Thread thread = new Thread(threadStart);

            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
        }

        /// <summary>
        /// Do in a thread
        /// </summary>
        private void ThrowException_BackgroundWorker()
        {
            Exception e = GetException();

            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += delegate
            {
                ThrowExceptionInTry(e);
            };

            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Do in a thread
        /// </summary>
        private void ThrowException_Task()
        {
            Exception e = GetException();

            var task = new Task(() =>
            {
                ThrowExceptionInTry(e);
            });

            task.Start();
        }

        /// <summary>
        /// Do in a thread
        /// </summary>
        private void ThrowException_TaskWait()
        {
            Exception e = GetException();

            var task = new Task(() =>
            {
                Task.Factory.StartNew(() =>
                {
                    Task.Factory.StartNew(() =>
                    {
                        ThrowException(e);
                    },
                    TaskCreationOptions.AttachedToParent);

                },
                TaskCreationOptions.AttachedToParent);
            });

            try
            {
                task.Start();
                task.Wait();
            }
            finally
            {
                Application.Current.Dispatcher.InvokeAsync(new Action(() =>
                {
                    var vm = ((App)Application.Current).ViewModel;
                    int pos = vm.Add("finally");
                    MiniDumpWriter.Write(pos, "finally");
                }));
            }
        }

        /// <summary>
        /// Do in a thread
        /// </summary>
        private void ThrowException_MainDispatcher()
        {
            Exception e = GetException();

            // Place delegate on the Dispatcher. 
            this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { ThrowExceptionInTry(e); }));
        }

        private void ThrowException_SeparateAppDomain()
        {
            var domain = AppDomain.CreateDomain("DumpMe_AppDomain");
            try
            {
                domain.ExecuteAssembly("DumpMe_AppDomain.exe");
            }
            finally
            {
                AppDomain.Unload(domain);

                Application.Current.Dispatcher.InvokeAsync(new Action(() =>
                {
                    var vm = ((App)Application.Current).ViewModel;
                    int pos = vm.Add("finally");
                    MiniDumpWriter.Write(pos, "finally");
                }));
            }
        }

        private void ThrowException_ComInterop()
        {
            CrashMeCOM.interop.CrashMe dumpMe = new CrashMeCOM.interop.CrashMe();
            dumpMe.Throw_E_FAIL();
        }


        private void ThrowException(ThrowWhere where)
        {
            ((App)Application.Current).ViewModel.Reset();

            switch (where)
            {
                case ThrowWhere.MainThread:
                    ThrowException_MainThread();
                    break;
                case ThrowWhere.Thread:
                    ThrowException_Thread();
                    break;
                case ThrowWhere.BackgroundUIThread:
                    ThrowException_BackgroundUiThread();
                    break;
                case ThrowWhere.BackgroundWorker:
                    ThrowException_BackgroundWorker();
                    break;
                case ThrowWhere.Task:
                    ThrowException_Task();
                    break;
                case ThrowWhere.TaskWait:
                    ThrowException_TaskWait();
                    break;
                case ThrowWhere.MainDispatcher:
                    ThrowException_MainDispatcher();
                    break;
                case ThrowWhere.SeparateAppDomain:
                    ThrowException_SeparateAppDomain();
                    break;
                case ThrowWhere.ComInterop:
                    ThrowException_ComInterop();
                    break;
                default:
                    break;
            }
        }

        enum ThrowWhere
        {
            MainThread,
            MainDispatcher,

            Thread,
            BackgroundUIThread,

            Task,
            TaskWait,

            BackgroundWorker,

            SeparateAppDomain,
            ComInterop
        }

        #endregion

        #region UI events

        private void ThrowException_MainThread(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.MainThread);
        }

        private void ThrowException_Thread(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.Thread);
        }

        private void ThrowException_BackgroundUIThread(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.BackgroundUIThread);
        }

        private void ThrowException_BackgroundWorker(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.BackgroundWorker);
        }

        private void ThrowException_Task(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.Task);
        }

        private void ThrowException_TaskWait(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.TaskWait);
        }

        private void ThrowException_MainDispatcher(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.MainDispatcher);
        }

        private void ThrowException_SeparateAppDomain(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.SeparateAppDomain);
        }

        private void ThrowException_COMInterop(object sender, RoutedEventArgs e)
        {
            ThrowException(ThrowWhere.ComInterop);
        }

        #endregion







    }
}
